package gmongo

import (
	"strings"

	log "gitee.com/abril-cx/gtools/glog"
	utils "gitee.com/abril-cx/gtools/gutils"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	mon "go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

type ConnFilter struct {
	model  string
	filter bson.M
	fields []string
	sort   *bson.D
	limit  *int64
	skip   *int64
	depth  *int64
}

func (c *ConnFilter) formatSelect() bson.D {
	result := bson.D{}
	for _, field := range c.fields {
		if field != "_id" {
			result = append(result, bson.E{Key: field, Value: 1})
		}
	}
	if utils.InArrayString(c.fields, "_id") {
		result = append(result, bson.E{Key: "_id", Value: 0})
	}
	return result
}

func (c *ConnFilter) checkFilter(data bson.M) (bson.M, error) {
	if len(c.fields) != 0 {
		for k := range data {
			if !utils.InArrayString(c.fields, k) {
				delete(data, k)
			}
		}
	}
	fields, err := GetFields(c.model)
	if err != nil {
		return nil, log.ErrorReturn(err.Error())
	}
	data = FiltrationData(fields, data)
	return data, nil
}

func (c *ConnFilter) depthFind(results interface{}) {
	fields, err := GetFields(c.model)
	if err != nil {
		log.Error(err.Error())
		return
	}
	field_data := [][]string{}
	for _, item := range fields {
		field_str := item["field"].(string)
		bind := item["bind"]
		if bind == nil {
			continue
		}
		bind_str := bind.(string)
		set_data := []string{field_str, bind_str}
		if strings.HasSuffix(field_str, "_id") {
			field_data = append(field_data, set_data)
		} else if strings.HasSuffix(field_str, "_ids") {
			field_data = append(field_data, set_data)
		}
	}
	for _, result := range results.([]bson.M) {
		for _, item := range field_data {
			field := item[0]
			bind_model_list := strings.Split(item[1], ".")
			service := bind_model_list[0]
			model := bind_model_list[1]
			if service == SERVICE {
				if strings.HasSuffix(field, "_id") && field != "_id" {
					if result[field] == nil {
						continue
					}
					value_id := result[field].(primitive.ObjectID)
					result_data := bson.M{}
					err := Conn.Model(model).Filter(bson.M{"_id": value_id}).Depth(*c.depth - 1).FindOne(&result_data)
					if err != nil {
						log.Error(err)
						continue
					}
					result[field] = result_data
				} else if strings.HasSuffix(field, "_ids") {
					if result[field] == nil {
						continue
					}
					values_ids := result[field].(primitive.A)
					ids := []primitive.ObjectID{}
					for _, value_id := range values_ids {
						ids = append(ids, value_id.(primitive.ObjectID))
					}
					results_data := []bson.M{}
					err = Conn.Model(model).Filter(bson.M{"_id": bson.M{"$in": ids}}).Depth(*c.depth - 1).Find(&results_data)
					if err != nil {
						log.Error(err)
						continue
					}
					result[field] = results_data
				}
			} else {
				// TODO
				// 跨服务查询
			}
		}
	}
}

func (c *ConnFilter) Find(results interface{}, opts ...*options.FindOptions) error {
	var opts_new *options.FindOptions
	if len(opts) != 0 {
		opts_new = opts[0]
	} else {
		opts_new = &options.FindOptions{}
	}
	if len(c.fields) != 0 {
		opts_new.SetProjection(c.formatSelect())
	}
	if c.skip != nil {
		opts_new.SetSkip(*c.skip)
	}
	if c.limit != nil {
		opts_new.SetLimit(*c.limit)
	}
	if c.sort != nil {
		opts_new.SetSort(*c.sort)
	}
	cur, err := MongoConn.Collection(c.model).Find(Ctx, c.filter, opts_new)
	if err != nil {
		return log.WarningReturn(err.Error())
	}
	results_bson := []bson.M{}
	err = cur.All(Ctx, &results_bson)
	if err != nil {
		return log.ErrorReturn(err.Error())
	}
	if c.depth != nil && *c.depth > 0 {
		c.depthFind(results_bson)
	}
	bt, results_bson_data, err := bson.MarshalValue(&results_bson)
	if err != nil {
		return err
	}
	err = bson.UnmarshalValue(bt, results_bson_data, results)
	if err != nil {
		return err
	}
	return nil
}

func (c *ConnFilter) FindOne(result interface{}, opts ...*options.FindOneOptions) error {
	var opts_new *options.FindOneOptions
	if len(opts) != 0 {
		opts_new = opts[0]
	} else {
		opts_new = &options.FindOneOptions{}
	}
	if len(c.fields) != 0 {
		opts_new.SetProjection(c.formatSelect())
	}
	single_result := MongoConn.Collection(c.model).FindOne(Ctx, c.filter, opts_new)
	if single_result.Err() != nil {
		return single_result.Err()
	}
	result_bson := bson.M{}
	err := single_result.Decode(&result_bson)
	if err != nil {
		return err
	}
	if c.depth != nil && *c.depth > 0 {
		c.depthFind([]bson.M{result_bson})
	}
	bt, results_bson_data, err := bson.MarshalValue(&result_bson)
	if err != nil {
		return log.ErrorReturn(err.Error())
	}
	err = bson.UnmarshalValue(bt, results_bson_data, result)
	if err != nil {
		return log.ErrorReturn(err.Error())
	}
	return nil
}

func (c *ConnFilter) UpdateOne(data bson.M, opts ...*options.UpdateOptions) (*mon.UpdateResult, error) {
	if len(c.fields) != 0 {
		result, err := c.checkFilter(data)
		if err != nil {
			return nil, log.ErrorReturn(err.Error())
		}
		data = result
	}
	DefaultUpdate(data)
	result, err := MongoConn.Collection(c.model).UpdateOne(Ctx, c.filter, bson.M{
		"$set": data,
	}, opts...)
	if err != nil {
		return nil, log.ErrorReturn(err.Error())
	}
	return result, err
}

func (c *ConnFilter) UpdateMany(data bson.M, opts ...*options.UpdateOptions) (*mon.UpdateResult, error) {
	if len(c.fields) != 0 {
		result, err := c.checkFilter(data)
		if err != nil {
			return nil, log.ErrorReturn(err.Error())
		}
		data = result
	}
	DefaultUpdate(data)
	result, err := MongoConn.Collection(c.model).UpdateMany(Ctx, c.filter, bson.M{
		"$set": data,
	}, opts...)
	if err != nil {
		return nil, log.ErrorReturn(err.Error())
	}
	return result, err
}

func (c *ConnFilter) UpdateOrCreate(data bson.M) error {
	// 先查询
	result := MongoConn.Collection(c.model).FindOne(Ctx, c.filter)
	if result.Err() != nil {
		// 没有查询到，则插入
		fields, err := GetFields(c.model)
		if err != nil {
			return log.ErrorReturn(err.Error())
		}
		data = FiltrationData(fields, data)
		data, err := DefaultModel(fields, data)
		if err != nil {
			return log.ErrorReturn(err.Error())
		}
		DefaultCreate(data)
		_, err = MongoConn.Collection(c.model).InsertOne(Ctx, data)
		if err != nil {
			return log.ErrorReturn(err.Error())
		}
		return nil
	}
	if len(c.fields) != 0 {
		result, err := c.checkFilter(data)
		if err != nil {
			return log.ErrorReturn(err.Error())
		}
		data = result
	}
	DefaultUpdate(data)
	// 查询到，则更新
	_, err := MongoConn.Collection(c.model).UpdateOne(Ctx, c.filter, bson.M{
		"$set": data,
	})
	if err != nil {
		return log.ErrorReturn(err.Error())
	}
	return nil
}

// true 真删除
func (c *ConnFilter) Delete(is_delete bool) error {
	if is_delete {
		_, err := MongoConn.Collection(c.model).DeleteMany(Ctx, c.filter)
		return err
	}
	_, err := MongoConn.Collection(c.model).UpdateMany(Ctx, c.filter, bson.M{"$set": bson.M{"deleted": utils.GetDateTime(0)}})
	return err
}

func (c *ConnFilter) Count(opts ...*options.CountOptions) (int64, error) {
	return MongoConn.Collection(c.model).CountDocuments(Ctx, c.filter, opts...)
}

// created,-name
func (c *ConnFilter) Sort(sort_str string) *ConnFilter {
	sort_data := bson.D{}
	if sort_str == "" {
		return c
	}
	for _, v := range strings.Split(sort_str, ",") {
		if v[0] == '-' {
			sort_data = append(sort_data, bson.E{Key: v[1:], Value: -1})
		} else {
			sort_data = append(sort_data, bson.E{Key: v, Value: 1})
		}
	}
	c.sort = &sort_data
	return c
}

func (c *ConnFilter) Limit(limit int64) *ConnFilter {
	c.limit = &limit
	return c
}

func (c *ConnFilter) Skip(skip int64) *ConnFilter {
	c.skip = &skip
	return c
}

// 使用此方法必须使用 bosn.M{} 接收数据
func (c *ConnFilter) Depth(depth int64) *ConnFilter {
	c.depth = &depth
	return c
}
